package com.xuan.utils.xuan.bazi.utils;

import com.nlf.calendar.JieQi;
import com.nlf.calendar.Lunar;
import com.nlf.calendar.Solar;
import com.nlf.calendar.eightchar.*;
import com.xuan.utils.common.DateUtil;
import com.xuan.utils.xuan.bazi.BaZiMap;
import lombok.Data;
import org.apache.commons.lang3.StringUtils;

import java.util.*;

/**
 * 八字-大运流年工具
 *
 * @author 善待
 */
@Data
public class BaZiDaYunLiuNianUtil {

    /**
     * 农历日期
     */
    private Lunar lunar;
    /**
     * 日干
     */
    private String dayGan;
    /**
     * 性别（0:女。1:男）
     */
    private int sex;
    /**
     * 起运流派（0:按天数和时辰数计算：3天1年、1天4个月、1时辰10天。1:按分钟数计算：4320分=1年、360分=1月、12分=1天、1分=2小时）
     */
    private int qiYunLiuPai;

    /**
     * 大运数据
     */
    private List<List<String>> daYun;
    /**
     * 流年数据
     */
    private List<List<String>> liuNian;
    /**
     * 小运数据
     */
    private List<List<String>> xiaoYun;
    /**
     * 流月数据
     */
    private List<List<String>> liuYue;
    /**
     * 流日数据
     */
    private List<List<String>> liuRi;
    /**
     * 流时数据
     */
    private List<List<String>> liuShi;

    /**
     * 大运原数据
     */
    private DaYun[] daYunSource;
    /**
     * 流年原数据
     */
    private LiuNian[] liuNianSource;

//****************************************************************************************************************************************************

    /**
     * 初始化
     *
     * @param date        日期
     * @param dayGan      日干
     * @param sex         性别（0:女。1:男）
     * @param qiYunLiuPai 起运流派（0:按天数和时辰数计算：3天1年、1天4个月、1时辰10天。1:按分钟数计算：4320分=1年、360分=1月、12分=1天、1分=2小时）
     */
    public BaZiDaYunLiuNianUtil(Date date, String dayGan, int sex, int qiYunLiuPai) {

        this.lunar = new Lunar(date); // 农历日期（TODO：暂不支持四柱设置）
        this.dayGan = dayGan; // 日干
        this.sex = sex; // 性别（0:女。1:男）
        this.qiYunLiuPai = qiYunLiuPai; // 起运流派（0:按天数和时辰数计算：3天1年、1天4个月、1时辰10天。1:按分钟数计算：4320分=1年、360分=1月、12分=1天、1分=2小时）

    }

//====================================================================================================================================================

    /**
     * 测试
     */
    public static void main(String[] args) {

        Date date = new Date(); // 日期
        Lunar lunar = new Lunar(date);

        int sex = 1; // 性别（0:女。1:男）
        int qiYunLiuPai = 0; // 起运流派（0:按天数和时辰数计算：3天1年、1天4个月、1时辰10天。1:按分钟数计算：4320分=1年、360分=1月、12分=1天、1分=2小时）
        int daYunLun = 1; // 大运轮
        int liuNianLun = 1; // 流年轮

        BaZiDaYunLiuNianUtil util = new BaZiDaYunLiuNianUtil(date, lunar.getDayInGanZhiExact2(), sex, qiYunLiuPai);
        util.daYun(); // 计算大运数据
        util.liuNian(daYunLun); // 计算流年数据
        util.xiaoYun(daYunLun); // 计算小运数据
        util.liuYue(daYunLun, liuNianLun, lunar.getSolar().getYear()); // 计算流月数据
        util.liuRi(lunar.getSolar().getYear(), 2); // 计算流月数据
        util.liuShi(lunar.getSolar().getYear(), 2, 4, 0); // 计算流时数据

        System.out.println("【大运】 --- " + util.getDaYun());
        System.out.println();
        System.out.println("【流年】 --- " + util.getLiuNian());
        System.out.println();
        System.out.println("【小运】 --- " + util.getXiaoYun());
        System.out.println();
        System.out.println("【流月】 --- " + util.getLiuYue());
        System.out.println();
        System.out.println("【流日】 --- " + util.getLiuRi());
        System.out.println();
        System.out.println("【流时】 --- " + util.getLiuShi());

    }

    /**
     * 计算大运数据（公历年、年龄、干支、天干十神、地支十神）
     */
    public void daYun() {

        List<List<String>> daYunList = new ArrayList<>(); // 大运

        // 1、获取运
        Yun yun = getLunar().getEightChar().getYun(getSex(), getQiYunLiuPai());

        // 2、获取大运
        DaYun[] daYun = yun.getDaYun(16); // 获取大运
        String ganShen = ""; // 天干十神
        String zhiShen = ""; // 地支十神
        for (DaYun dy : daYun) {
            String dyGz = dy.getGanZhi(); // 每一个大运干支
            if (StringUtils.isNotBlank(dyGz)) {
                List<String> shiShen = getShiShen(dyGz);
                ganShen = shiShen.get(0); // 天干十神
                zhiShen = shiShen.get(1); // 地支十神
            }
            // 添加全部大运数据（年份、年龄、干支、天干十神、地支十神）
            List<String> list = Arrays.asList(String.valueOf(dy.getStartYear()), String.valueOf(dy.getStartAge()), dyGz, ganShen, zhiShen);
            daYunList.add(list);
        }

        // 3、设置数据
        this.daYun = daYunList; // 大运
        this.daYunSource = daYun; // 大运原数据

    }

    /**
     * 计算流年数据（公历年、年龄、干支、天干十神、地支十神）
     *
     * @param daYunLun 大运轮
     */
    public void liuNian(int daYunLun) {

        DaYun[] daYunSource = getDaYunSource(); // 大运原数据

        List<List<String>> liuNianAll = new ArrayList<>(); // 临时存储全部流年
        List<List<String>> liuNianList = new ArrayList<>(); // 流年

        // 1、添加全部流年数据（年份、年龄、干支）
        LiuNian[] liuNian = null;
        for (DaYun dy : daYunSource) {
            liuNian = dy.getLiuNian(10); // 获取流年
            for (LiuNian ln : liuNian) {
                List<String> list = Arrays.asList(String.valueOf(ln.getYear()), String.valueOf(ln.getAge()), ln.getGanZhi());
                liuNianAll.add(list);
            }
        }

        String ganShen = ""; // 天干十神
        String zhiShen = ""; // 地支十神

        // 2、计算小运持续的年数
        int xiaoYunLun = Integer.parseInt(getDaYun().get(1).get(0)) - Integer.parseInt(getDaYun().get(0).get(0));

        // 3、第一轮大运
        if (daYunLun == 1) {
            // 3.1、若小运持续的年数为0，则获取第一轮大运的十轮流年数据（说明：当起运时间小于1年时才会出现这种情况）
            if (xiaoYunLun == 0) {
                for (int i = 0; i < 10; i++) {
                    List<String> ln = liuNianAll.get(i);
                    String lnGz = ln.get(2); // 每一个流年干支
                    if (StringUtils.isNotBlank(lnGz)) {
                        List<String> shiShen = getShiShen(lnGz);
                        ganShen = shiShen.get(0); // 天干十神
                        zhiShen = shiShen.get(1); // 地支十神
                    }
                    // 添加流年数据（年份、年龄、干支、天干十神、地支十神）
                    List<String> list = Arrays.asList(ln.get(0), ln.get(1), lnGz, ganShen, zhiShen);
                    liuNianList.add(list);
                }
            }
            // 3.2、小运持续的年数不为0，则获取流年持续的年数中的数据
            if (xiaoYunLun != 0) {
                for (int i = 0; i < xiaoYunLun; i++) {
                    List<String> ln = liuNianAll.get(i);
                    String lnGz = ln.get(2); // 每一个流年干支
                    if (StringUtils.isNotBlank(lnGz)) {
                        List<String> shiShen = getShiShen(lnGz);
                        ganShen = shiShen.get(0); // 天干十神
                        zhiShen = shiShen.get(1); // 地支十神
                    }
                    // 添加流年数据（年份、年龄、干支、天干十神、地支十神）
                    List<String> list = Arrays.asList(ln.get(0), ln.get(1), lnGz, ganShen, zhiShen);
                    liuNianList.add(list);
                }
            }
        }

        // 4、非第一轮大运
        if (daYunLun != 1) {
            int start = xiaoYunLun + 10 * (daYunLun - 2); // 开始索引
            int end = start + 10; // 结束索引
            for (int i = start; i < end; i++) {
                List<String> ln = liuNianAll.get(i);
                String lnGz = ln.get(2); // 每一个流年干支
                if (StringUtils.isNotBlank(lnGz)) {
                    List<String> shiShen = getShiShen(lnGz);
                    ganShen = shiShen.get(0); // 天干十神
                    zhiShen = shiShen.get(1); // 地支十神
                }
                // 添加流年数据（年份、年龄、干支、天干十神、地支十神）
                List<String> list = Arrays.asList(ln.get(0), ln.get(1), lnGz, ganShen, zhiShen);
                liuNianList.add(list);
            }
        }

        this.liuNian = liuNianList; // 流年
        this.liuNianSource = liuNian; // 流年原数据

    }

    /**
     * 计算小运数据（公历年、年龄、干支、天干十神、地支十神）
     *
     * @param daYunLun 大运轮
     */
    public void xiaoYun(int daYunLun) {

        DaYun[] daYunSource = getDaYunSource(); // 大运原数据

        List<List<String>> xiaoYunAll = new ArrayList<>(); // 临时存储全部小运
        List<List<String>> xiaoYunList = new ArrayList<>(); // 小运

        // 1、添加全部小运数据（年份、年龄、干支）
        for (DaYun dy : daYunSource) {
            XiaoYun[] xiaoYun = dy.getXiaoYun(10); // 获取小运
            for (XiaoYun xy : xiaoYun) {
                List<String> list = Arrays.asList(String.valueOf(xy.getYear()), String.valueOf(xy.getAge()), xy.getGanZhi());
                xiaoYunAll.add(list);
            }
        }

        String ganShen = ""; // 天干十神
        String zhiShen = ""; // 地支十神

        // 2、计算小运持续的年数
        int xiaoYunLun = Integer.parseInt(getDaYun().get(1).get(0)) - Integer.parseInt(getDaYun().get(0).get(0));

        // 3、第一轮大运
        if (daYunLun == 1) {
            // 3.1、若小运持续的年数为0，则获取第一轮大运的十轮小运数据（说明：当起运时间小于1年时才会出现这种情况）
            if (xiaoYunLun == 0) {
                for (int i = 0; i < 10; i++) {
                    List<String> xy = xiaoYunAll.get(i);
                    String xyGz = xy.get(2); // 每一个小运干支
                    if (StringUtils.isNotBlank(xyGz)) {
                        List<String> shiShen = getShiShen(xyGz);
                        ganShen = shiShen.get(0); // 天干十神
                        zhiShen = shiShen.get(1); // 地支十神
                    }
                    // 添加小运数据（年份、年龄、干支、天干十神、地支十神）
                    List<String> list = Arrays.asList(xy.get(0), xy.get(1), xyGz, ganShen, zhiShen);
                    xiaoYunList.add(list);
                }
            }
            // 3.2、小运持续的年数不为0，则获取小运持续的年数中的数据
            if (xiaoYunLun != 0) {
                for (int i = 0; i < xiaoYunLun; i++) {
                    List<String> xy = xiaoYunAll.get(i);
                    String xyGz = xy.get(2); // 每一个小运干支
                    if (StringUtils.isNotBlank(xyGz)) {
                        List<String> shiShen = getShiShen(xyGz);
                        ganShen = shiShen.get(0); // 天干十神
                        zhiShen = shiShen.get(1); // 地支十神
                    }
                    // 添加小运数据（年份、年龄、干支、天干十神、地支十神）
                    List<String> list = Arrays.asList(xy.get(0), xy.get(1), xyGz, ganShen, zhiShen);
                    xiaoYunList.add(list);
                }
            }
        }

        // 4、非第一轮大运
        if (daYunLun != 1) {
            int start = xiaoYunLun + 10 * (daYunLun - 2); // 开始索引
            int end = start + 10; // 结束索引
            for (int i = start; i < end; i++) {
                List<String> xy = xiaoYunAll.get(i);
                String xyGz = xy.get(2); // 每一个小运干支
                if (StringUtils.isNotBlank(xyGz)) {
                    List<String> shiShen = getShiShen(xyGz);
                    ganShen = shiShen.get(0); // 天干十神
                    zhiShen = shiShen.get(1); // 地支十神
                }
                // 添加小运数据（年份、年龄、干支、天干十神、地支十神）
                List<String> list = Arrays.asList(xy.get(0), xy.get(1), xyGz, ganShen, zhiShen);
                xiaoYunList.add(list);
            }
        }

        this.xiaoYun = xiaoYunList; // 小运

    }

    /**
     * 计算流月数据（公历月、节、日期、干支、天干十神、地支十神）
     *
     * @param daYunLun   大运轮
     * @param liuNianLun 流年轮
     * @param solarYear  公历年
     */
    public void liuYue(int daYunLun, int liuNianLun, int solarYear) {

        String[] shiErJie = BaZiMap.SHI_ER_JIE; // 十二节
        String[] shiErSolarMonth = BaZiMap.SHI_ER_SOLAR_MONTH; // 十二公历月

        // 1、添加全部大运流月数据（公历月、节、日期、干支）
        List<String> liuYueDate = getLiuYueDate(solarYear); // 计算并返回流月的十二月具体日期
        List<List<String>> daYunLiuYueTemporary = new ArrayList<>(); // 临时存储全部大运流月
        for (LiuNian ln : getLiuNianSource()) {
            LiuYue[] liuYueSource = ln.getLiuYue(); // 获取流月
            for (int i = 0; i < liuYueSource.length; i++) {
                daYunLiuYueTemporary.add(Arrays.asList(shiErSolarMonth[i], shiErJie[i], liuYueDate.get(i), liuYueSource[i].getGanZhi()));
            }
        }

        // 2、遍历大运流月，查找月干支所处的索引
        int index = 0;
        String monthGanZhi = new Lunar(getLunar().getYear(), 1, 15).getMonthInGanZhiExact(); // 传入日期的立春当月的干支
        for (int i = 0; i < daYunLiuYueTemporary.size(); i++) {
            String ganZhi = daYunLiuYueTemporary.get(i).get(3);
            if (ganZhi.equals(monthGanZhi)) {
                index = i;
                break;
            } else {
                index++;
            }
        }

        // 3、计算小运持续的年数
        int xiaoYunLun = Integer.parseInt(getDaYun().get(1).get(0)) - Integer.parseInt(getDaYun().get(0).get(0));
        if (index == 1 || xiaoYunLun == 10) index = 0;

        // 4、从月干支索引处向后依次添加流月数据（公历月、节、日期、干支）
        List<List<String>> xiaoYunLiuYueTemporary = new ArrayList<>(); // 临时存储全部小运流月
        int xiaoYunLiuYueCount = xiaoYunLun * 12;
        for (int i = 0; i < xiaoYunLiuYueCount; i++) {
            List<String> ly = daYunLiuYueTemporary.get(i + index);
            xiaoYunLiuYueTemporary.add(Arrays.asList(ly.get(0), ly.get(1), ly.get(2), ly.get(3)));
        }

        String ganShen = ""; // 天干十神
        String zhiShen = ""; // 地支十神
        List<List<String>> liuYue = new ArrayList<>(); // 流月

        // 5、添加流月数据（公历月、节、日期、干支、天干十神、地支十神）
        int start; // 开始索引
        int end; // 结束索引
        if (daYunLun == 1) {
            if (xiaoYunLun == 0) {
                // 5.1、第一轮大运，若小运持续的年数为0，则获取第一轮大运中第一到十二轮流月数据（说明：当起运时间小于1年时才会出现这种情况）
                for (int i = 0; i < 12; i++) {
                    List<String> ly = daYunLiuYueTemporary.get(i);
                    String liuYueGanZhi = ly.get(3); // 每一个流月干支
                    if (StringUtils.isNotBlank(liuYueGanZhi)) {
                        List<String> shiShen = getShiShen(liuYueGanZhi);
                        ganShen = shiShen.get(0); // 天干十神
                        zhiShen = shiShen.get(1); // 地支十神
                    }
                    liuYue.add(Arrays.asList(ly.get(0), ly.get(1), ly.get(2), liuYueGanZhi, ganShen, zhiShen));
                }
            } else {
                // 5.2、第一轮大运，小运持续的年数不为0，则获取小运的十二轮流月数据
                start = 12 * (liuNianLun - 1); // 开始索引
                end = start + 12; // 结束索引
                for (int i = start; i < end; i++) {
                    List<String> ly = xiaoYunLiuYueTemporary.get(i);
                    String liuYueGanZhi = ly.get(3); // 每一个流月干支
                    if (StringUtils.isNotBlank(liuYueGanZhi)) {
                        List<String> shiShen = getShiShen(liuYueGanZhi);
                        ganShen = shiShen.get(0); // 天干十神
                        zhiShen = shiShen.get(1); // 地支十神
                    }
                    liuYue.add(Arrays.asList(ly.get(0), ly.get(1), ly.get(2), liuYueGanZhi, ganShen, zhiShen));
                }
            }
        } else {
            // 5.3、非第一轮大运
            start = (12 * (liuNianLun - 1)) == 120 ? 0 : 12 * (liuNianLun - 1); // 开始索引
            end = start + 12; // 结束索引
            for (int i = start; i < end; i++) {
                List<String> ly = daYunLiuYueTemporary.get(i);
                String liuYueGanZhi = ly.get(3); // 每一个流月干支
                if (StringUtils.isNotBlank(liuYueGanZhi)) {
                    List<String> shiShen = getShiShen(liuYueGanZhi);
                    ganShen = shiShen.get(0); // 天干十神
                    zhiShen = shiShen.get(1); // 地支十神
                }
                liuYue.add(Arrays.asList(ly.get(0), ly.get(1), ly.get(2), liuYueGanZhi, ganShen, zhiShen));
            }
        }

        this.liuYue = liuYue;

    }

    /**
     * 计算流日数据（公历日、农历日、干支、天干十神、地支十神）
     *
     * @param solarYear  公历年
     * @param solarMonth 公历月
     */
    public void liuRi(int solarYear, int solarMonth) {

        List<List<String>> liuRiList = new ArrayList<>(); // 流日

        if (solarMonth == 1) solarYear += 1;

        // 1、根据公历年、公历月初始化日期
        Lunar lunar = new Lunar(new Solar(solarYear, solarMonth, 15)); // TODO：暂时按第15天计算

        // 2、获取上一节、下一节
        JieQi prevJie = lunar.getPrevJie(true); // 上一节（TODO：暂时按天计算）
        JieQi nextJie = lunar.getNextJie(true); // 下一节（TODO：暂时按天计算）

        // 3、获取上一节的农历日期
        Lunar startLunar = prevJie.getSolar().getLunar();

        String ganShen = ""; // 天干十神
        String zhiShen = ""; // 地支十神

        // 4、从上一节的日期向后依次添加日期、干支、干支十神，直至碰到下一节结束
        for (int i = 0; i < 60; i++) {
            Solar nextSolar = startLunar.next(i).getSolar(); // 向后推第n天的公历日期
            Lunar nextLunar = startLunar.next(i); // 向后推第n天的农历日期
            // 若未到下一节则添加
            if (!nextLunar.getJie().equals(nextJie.toString())) {
                String dayGanZhi = nextLunar.getDayInGanZhiExact2(); // 获取日干支（TODO：晚子时日柱暂时按当天计算）
                if (StringUtils.isNotBlank(dayGanZhi)) {
                    List<String> shiShen = getShiShen(dayGanZhi);
                    ganShen = shiShen.get(0); // 天干十神
                    zhiShen = shiShen.get(1); // 地支十神
                }
                // 添加流日数据（公历日、农历日、干支、天干十神、地支十神）
                List<String> list = Arrays.asList(String.valueOf(nextSolar.getDay()), nextLunar.getDayInChinese(), nextLunar.getSolar().getDay() + "日", dayGanZhi, ganShen, zhiShen);
                liuRiList.add(list);
            } else break;
        }

        this.liuRi = liuRiList;

    }

//----------------------------------------------------------------------------------------------------------------------------------------------------

    /**
     * 计算流时数据（公历时、干支、天干十神、地支十神）
     *
     * @param solarYear  公历年
     * @param solarMonth 公历月
     * @param solarDay   公历日
     * @param nextDay    向后第几天
     */
    public void liuShi(int solarYear, int solarMonth, int solarDay, int nextDay) {

        String[] shiErShi = BaZiMap.SHI_ER_SHI; // 十二时

        List<List<String>> liuShiList = new ArrayList<>(); // 流时

        String ganShen = ""; // 天干十神
        String zhiShen = ""; // 地支十神

        // 1、计算十二时辰、干支
        int hour = 0; // 小时数
        Solar solar = new Solar(solarYear, solarMonth, solarDay).next(nextDay - 1);
        for (int i = 0; i < 12; i++) {
            // 通过公历日期初始化
            Lunar lunar = new Lunar(DateUtil.timeToDate(solar.getYear(), solar.getMonth(), solar.getDay(), hour, 0, 0));
            String hourGanZhi = lunar.getTimeInGanZhi(); // 每一时辰的干支
            if (StringUtils.isNotBlank(hourGanZhi)) {
                List<String> shiShen = getShiShen(lunar.getTimeInGanZhi());
                ganShen = shiShen.get(0); // 天干十神
                zhiShen = shiShen.get(1); // 地支十神
            }
            // 添加流时数据（公历时、干支、天干十神、地支十神）
            List<String> list = Arrays.asList(shiErShi[i], hourGanZhi, ganShen, zhiShen);
            liuShiList.add(list);
            hour += 2;
        }

        this.liuShi = liuShiList;

    }

    /**
     * 计算并返回十神（天干十神、地支十神）
     *
     * @param ganZhi 干支
     * @return 十神（天干十神、地支十神）
     */
    private List<String> getShiShen(String ganZhi) {

        List<String> shiShen = new ArrayList<>();
        Map<String, String> shiShenMap = BaZiMap.SHI_SHEN_YUN_NIAN_MAP; // 十神，用于大运流年

        String gan = ganZhi.substring(0, 1); // 天干
        String zhi = ganZhi.substring(1, 2); // 地支
        String ganShiShen = shiShenMap.get(getDayGan() + gan); // 天干十神
        String zhiShiShen = shiShenMap.get(getDayGan() + zhi); // 地支十神
        shiShen.add(ganShiShen);
        shiShen.add(zhiShiShen);

        return shiShen;

    }

//----------------------------------------------------------------------------------------------------------------------------------------------------

    /**
     * 计算并返回流月的十二月具体日期
     *
     * @param solarYear 公历年
     * @return 流月的十二月具体日期
     */
    private List<String> getLiuYueDate(int solarYear) {

        String[] shiErJie = BaZiMap.SHI_ER_JIE; // 十二节

        // 1、初始化日期
        Lunar lunar = new Solar(solarYear, 1, 1).getLunar();

        int i = 0; // 十二节索引
        int day = 0; // 向后查询的天数
        List<String> list = new ArrayList<>(); // 保存流月的十二月具体日期
        while (true) {
            Lunar nextLunar = lunar.next(day); // 向后第n天的农历日期
            if (nextLunar.getJie().equals(shiErJie[i])) {
                Solar nextSolar = nextLunar.getSolar(); // 向后第n天的公历日期
                list.add(nextSolar.getMonth() + "月" + nextSolar.getDay() + "日");
                day += 26; // 每一个相邻节之间至少相差的天数
                i++;
                if (i > 11) break;
            }
            day++;
        }

        return list;

    }


}
